/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | foam-extend: Open Source CFD
   \\    /   O peration     | Version:     4.1
    \\  /    A nd           | Web:         http://www.foam-extend.org
     \\/     M anipulation  | For copyright notice see file Copyright
-------------------------------------------------------------------------------
License
	This file is part of foam-extend.

	foam-extend is free software: you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the
	Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	foam-extend is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with foam-extend.  If not, see <http://www.gnu.org/licenses/>.

\*---------------------------------------------------------------------------*/

#include "treeDataTriSurface.H"
#include "triSurfaceTools.H"
#include "triangleFuncs.H"
#include "indexedOctree.H"

// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //

defineTypeNameAndDebug(Foam::treeDataTriSurface, 0);


// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //

// Fast distance to triangle calculation. From
// "Distance Between Point and Trangle in 3D"
// David Eberly, Magic Software Inc. Aug. 2003.
// Works on function Q giving distance to point and tries to minimize this.
Foam::scalar Foam::treeDataTriSurface::nearestCoords
(
	const point& base,
	const point& E0,
	const point& E1,
	const scalar a,
	const scalar b,
	const scalar c,
	const point& P,
	scalar& s,
	scalar& t
)
{
	// distance vector
	const vector D(base - P);

	// Precalculate distance factors.
	const scalar d = E0 & D;
	const scalar e = E1 & D;

	// Do classification
	const scalar det = a*c - b*b;

	s = b*e - c*d;
	t = b*d - a*e;

	if (s+t < det)
	{
		if (s < 0)
		{
			if (t < 0)
			{
				//region 4
				if (e > 0)
				{
					//min on edge t = 0
					t = 0;
					s = (d >= 0 ? 0 : (-d >= a ? 1 : -d/a));
				}
				else
				{
					//min on edge s=0
					s = 0;
					t = (e >= 0 ? 0 : (-e >= c ? 1 : -e/c));
				}
			}
			else
			{
				//region 3. Min on edge s = 0
				s = 0;
				t = (e >= 0 ? 0 : (-e >= c ? 1 : -e/c));
			}
		}
		else if (t < 0)
		{
			//region 5
			t = 0;
			s = (d >= 0 ? 0 : (-d >= a ? 1 : -d/a));
		}
		else
		{
			//region 0
			const scalar invDet = 1/det;
			s *= invDet;
			t *= invDet;
		}
	}
	else
	{
		if (s < 0)
		{
			//region 2
			const scalar tmp0 = b + d;
			const scalar tmp1 = c + e;
			if (tmp1 > tmp0)
			{
				//min on edge s+t=1
				const scalar numer = tmp1 - tmp0;
				const scalar denom = a-2*b+c;
				s = (numer >= denom ? 1 : numer/denom);
				t = 1 - s;
			}
			else
			{
				//min on edge s=0
				s = 0;
				t = (tmp1 <= 0 ? 1 : (e >= 0 ? 0 : - e/c));
			}
		}
		else if (t < 0)
		{
			//region 6
			const scalar tmp0 = b + d;
			const scalar tmp1 = c + e;
			if (tmp1 > tmp0)
			{
				//min on edge s+t=1
				const scalar numer = tmp1 - tmp0;
				const scalar denom = a-2*b+c;
				s = (numer >= denom ? 1 : numer/denom);
				t = 1 - s;
			}
			else
			{
				//min on edge t=0
				t = 0;
				s = (tmp1 <= 0 ? 1 : (d >= 0 ? 0 : - d/a));
			}
		}
		else
		{
			//region 1
			const scalar numer = c+e-(b+d);
			if (numer <= 0)
			{
				s = 0;
			}
			else
			{
				const scalar denom = a-2*b+c;
				s = (numer >= denom ? 1 : numer/denom);
			}
		}
		t = 1 - s;
	}


	// Calculate distance.
	// Note: abs should not be needed but truncation error causes problems
	// with points very close to one of the triangle vertices.
	// (seen up to -9e-15). Alternatively add some small value.

	const scalar f = D & D;
	return Foam::mag(a*s*s + 2*b*s*t + c*t*t + 2*d*s + 2*e*t + f);
}


// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //

// Construct from components
Foam::treeDataTriSurface::treeDataTriSurface(const triSurface& surface)
:
	surface_(surface)
{}


// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //

Foam::pointField Foam::treeDataTriSurface::points() const
{
	const pointField& points = surface_.points();

	pointField centres(surface_.size());

	forAll(surface_, triI)
	{
		centres[triI] = surface_[triI].centre(points);
	}
	return centres;
}


//- Get type of sample (inside/outside/mixed) w.r.t. surface.
Foam::label Foam::treeDataTriSurface::getVolumeType
(
	const indexedOctree<treeDataTriSurface>& tree,
	const point& sample
) const
{
	// Find nearest point
	const treeBoundBox& treeBb = tree.bb();

	pointIndexHit pHit = tree.findNearest
	(
		sample,
		max
		(
			sqr(GREAT),
			magSqr(treeBb.span())
		)
	);

	if (!pHit.hit())
	{
		FatalErrorIn("treeDataTriSurface::getVolumeType(..)")
			<< "treeBb:" << treeBb
			<< " sample:" << sample
			<< " pHit:" << pHit
			<< abort(FatalError);
	}

	triSurfaceTools::sideType t = triSurfaceTools::surfaceSide
	(
		surface_,
		sample,
		pHit.index(),
		pHit.hitPoint(),
		indexedOctree<treeDataTriSurface>::perturbTol()
	);

	if (t == triSurfaceTools::UNKNOWN)
	{
		return indexedOctree<treeDataTriSurface>::UNKNOWN;
	}
	else if (t == triSurfaceTools::INSIDE)
	{
		return indexedOctree<treeDataTriSurface>::INSIDE;
	}
	else if (t == triSurfaceTools::OUTSIDE)
	{
		return indexedOctree<treeDataTriSurface>::OUTSIDE;
	}
	else
	{
		FatalErrorIn("treeDataTriSurface::getVolumeType(..)")
			<< "problem" << abort(FatalError);
		return indexedOctree<treeDataTriSurface>::UNKNOWN;
	}
}


// Check if any point on triangle is inside cubeBb.
bool Foam::treeDataTriSurface::overlaps
(
	const label index,
	const treeBoundBox& cubeBb
) const
{
	const pointField& points = surface_.points();
	const labelledTri& f = surface_[index];

	// Triangle points
	const point& p0 = points[f[0]];
	const point& p1 = points[f[1]];
	const point& p2 = points[f[2]];

	treeBoundBox triBb(p0, p0);
	triBb.min() = min(triBb.min(), p1);
	triBb.min() = min(triBb.min(), p2);

	triBb.max() = max(triBb.max(), p1);
	triBb.max() = max(triBb.max(), p2);

	//- For testing: robust one
	//return cubeBb.overlaps(triBb);

	//- Exact test of triangle intersecting bb

	// Quick rejection. If whole bounding box of tri is outside cubeBb then
	// there will be no intersection.
	if (!cubeBb.overlaps(triBb))
	{
		return false;
	}

	// Check if one or more triangle point inside
	if (cubeBb.contains(p0) || cubeBb.contains(p1) || cubeBb.contains(p2))
	{
		// One or more points inside
		return true;
	}

	// Now we have the difficult case: all points are outside but connecting
	// edges might go through cube. Use fast intersection of bounding box.

	//return triangleFuncs::intersectBbExact(p0, p1, p2, cubeBb);
	return triangleFuncs::intersectBb(p0, p1, p2, cubeBb);
}


// Calculate nearest point to sample. Updates (if any) nearestDistSqr, minIndex,
// nearestPoint.
void Foam::treeDataTriSurface::findNearest
(
	const labelList& indices,
	const point& sample,

	scalar& nearestDistSqr,
	label& minIndex,
	point& nearestPoint
) const
{
	const pointField& points = surface_.points();

	forAll(indices, i)
	{
		label index = indices[i];
		const labelledTri& f = surface_[index];

		// Triangle points
		const point& p0 = points[f[0]];
		const point& p1 = points[f[1]];
		const point& p2 = points[f[2]];


		////- Possible optimization: do quick rejection of triangle if bounding
		////  sphere does not intersect triangle bounding box. From simplistic
		////  test was not found to speed up things.
		//
		//// Triangle bounding box.
		//point triBbMin = min(p0, min(p1, p2));
		//point triBbMax = max(p0, max(p1, p2));
		//
		//if
		//(
		//    indexedOctree<treeDataTriSurface>::intersects
		//    (
		//        triBbMin,
		//        triBbMax,
		//        nearestDistSqr,
		//        sample
		//    )
		//)
		{
			// Get spanning vectors of triangle
			vector base(p1);
			vector E0(p0 - p1);
			vector E1(p2 - p1);

			scalar a(E0& E0);
			scalar b(E0& E1);
			scalar c(E1& E1);

			// Get nearest point in s,t coordinates (s is along E0, t is along
			// E1)
			scalar s;
			scalar t;

			scalar distSqr = nearestCoords
			(
				base,
				E0,
				E1,
				a,
				b,
				c,
				sample,

				s,
				t
			);

			if (distSqr < nearestDistSqr)
			{
				nearestDistSqr = distSqr;
				minIndex = index;
				nearestPoint = base + s*E0 + t*E1;
			}
		}
	}
}


// Calculate nearest point to line. Updates (if any) nearestDistSqr, minIndex,
// nearestPoint.
void Foam::treeDataTriSurface::findNearest
(
	const labelList& indices,
	const linePointRef& ln,

	treeBoundBox& tightest,
	label& minIndex,
	point& linePoint,
	point& nearestPoint
) const
{
	notImplemented
	(
		"treeDataTriSurface::findNearest(const labelList&"
		", const linePointRef&, treeBoundBox&, label&, point&, point&) const"
	);
}


bool Foam::treeDataTriSurface::intersects
(
	const label index,
	const point& start,
	const point& end,
	point& intersectionPoint
) const
{
	const pointField& points = surface_.points();

	const labelledTri& f = surface_[index];

	// Do quick rejection test
	treeBoundBox triBb(points[f[0]], points[f[0]]);
	triBb.min() = min(triBb.min(), points[f[1]]);
	triBb.max() = max(triBb.max(), points[f[1]]);
	triBb.min() = min(triBb.min(), points[f[2]]);
	triBb.max() = max(triBb.max(), points[f[2]]);

	const direction startBits(triBb.posBits(start));
	const direction endBits(triBb.posBits(end));

	if ((startBits & endBits) != 0)
	{
		// start and end in same block outside of triBb.
		return false;
	}

	const triPointRef tri(points[f[0]], points[f[1]], points[f[2]]);

	const vector dir(end - start);

	pointHit inter = tri.fastIntersection(start, dir, intersection::HALF_RAY);

	if (inter.hit() && inter.distance() <= 1)
	{
		// Note: no extra test on whether intersection is in front of us
		// since using half_ray.
		intersectionPoint = inter.hitPoint();

		return true;
	}
	else
	{
		return false;
	}


	//- Using exact intersections
	//pointHit info = f.tri(points).intersectionExact(start, end);
	//
	//if (info.hit())
	//{
	//    intersectionPoint = info.hitPoint();
	//}
	//return info.hit();
}


// ************************************************************************* //
